package com.demo.test.util;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.*;
import com.demo.test.entity.JarMethodDefinition;
import com.demo.test.entity.MethodParameterPo;
import com.test.interfaces.parentface.JarRuleParentFace;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.Data;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * 功能概述:jar规则工具类
 *
 * @author maoxiaomeng
 * @date 2022610
 */
@Slf4j
@Data
public class JarRuleHelper {


    /**
     * All targetClass
     */
    private Set<Class<?>> classSet;
    /**
     * Map<classPackage,List[className]>
     */
    private Map<String, List<String>> packageClassNameMap;
    private ClassLoader jarPackageLoader;
    private String filePath;
    private List<JarMethodDefinition> definitionList;

    private JarRuleHelper() {
    }

    @SneakyThrows
    public static JarRuleHelper start(@NonNull String jarPath, ClassLoader parentClassLoader) {
        if (!isValidJarFile(jarPath)) {
            throw new IllegalArgumentException("非jar包格式");
        }
        File jarFile = new File(jarPath);
        JarFile jar = new JarFile(jarFile);
        Map<String, List<String>> stringListMap = parsePackages(jar);
        // 创建URL数组，指定JAR包的路径
        URL[] urls = new URL[]{jarFile.toURI().toURL()};
        // 创建URLClassLoader实例，指定父ClassLoader为系统类加载器
        log.info("use {} as parent classloader", parentClassLoader.getClass().getName());
        JarRuleHelper jarRuleHelper = new JarRuleHelper();
        jarRuleHelper.packageClassNameMap = stringListMap;
        jarRuleHelper.jarPackageLoader = new URLClassLoader(urls, parentClassLoader);
        jarRuleHelper.classSet = new HashSet<>();
        jarRuleHelper.definitionList = new ArrayList<>();
        jarRuleHelper.filePath = jarPath;
        return jarRuleHelper;
    }

    public static JarRuleHelper start(String jarPath) {
        return start(jarPath, JarRuleParentFace.class.getClassLoader());
    }

    public static JarRuleHelper commonStart(String jarPath) {
        return start(jarPath, ClassLoader.getSystemClassLoader());
    }

    public void finish() {
        this.jarPackageLoader = null;
        this.packageClassNameMap = null;
        this.classSet = null;
    }

    /**
     * 1.获取jar文件,转换为file
     * 2.加载jar文件,获取所有实现了指定接口的实现类,按照 包路径-className 组装好
     * 3.解析class 的 所有的方法 结构 不入库
     * 4.模拟调用
     *
     * @return 方法定义信息
     */
    @SneakyThrows
    public JarRuleHelper parseDefinition() {
        log.info("加载jar临时文件本地地址[{}]", filePath);
        List<JarMethodDefinition> parentApiPos = new ArrayList<>();
        Map<String, List<String>> packageMapping = this.packageClassNameMap;
        Class<?> superClass = jarPackageLoader.loadClass(JarRuleParentFace.class.getName());
        Predicate<Class<?>> filter = clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz);

        //扫描指定包路径下的所有实现类
        log.info("当前使用的类加载器为:{}", jarPackageLoader.getClass().getName());
        packageMapping.forEach((basePackage, value) -> {
            try {
                if (jarPackageLoader != null) {
                    List<? extends Class<?>> collect = value.stream().map(clazzName -> {
                        try {
                            return jarPackageLoader.loadClass(clazzName);

                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                            throw new IllegalArgumentException("classNotFound:" + clazzName);
                        }
                    }).filter(filter).collect(Collectors.toList());
                    this.classSet.addAll(collect);
                }
            } catch (ArrayStoreException ex) {
                throw new IllegalArgumentException("包路径过于宽泛,无法找到准确的class,请精确路径");
            }
        });

        for (Class<?> aClass : this.classSet) {
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                //解析方法
                JarMethodDefinition parentApiPo = parseDefinition(declaredMethod);
                parentApiPo.setClassName(aClass.getName());
                parentApiPos.add(parentApiPo);
            }
        }
        this.definitionList = parentApiPos;
        return this;
    }

    /**
     * 方法调用
     *
     * @param methodName 方法名
     * @param className  class全限定名
     * @param inputParam 参数
     * @return 执行结果
     */
    public Object invokeMethod(String methodName, String className, Map<String, Object> inputParam) {
        try {
            if (methodName == null) {
                throw new IllegalArgumentException("调用的方法名称不允许为空");
            }
            if (className == null) {
                throw new IllegalArgumentException("调用的类名称不允许为空");
            }
            List<JarMethodDefinition> jarMethodDefinitions = this.definitionList;
            if (jarMethodDefinitions == null) {
                throw new IllegalArgumentException("未找到对应的包定义信息");
            }
            Predicate<JarMethodDefinition> filterCondition = e ->
                    e.getMethod().equals(methodName) && e.getClassName().equals(className);

            JarMethodDefinition jarMethodDefinition = jarMethodDefinitions.stream().filter(filterCondition)
                    .findAny()
                    .orElseThrow(() -> new IllegalArgumentException("指定包内未找到对应的方法定义"));

            String method = jarMethodDefinition.getMethod();

            Class<?> targetClass = this.classSet.stream().filter(e -> e.getName().equals(className)).findAny()
                    .orElseThrow(() -> new IllegalArgumentException("指定包内未找到对应的class"));

            Method[] declaredMethods = targetClass.getDeclaredMethods();

            Method targetMethod = Arrays.stream(declaredMethods).filter(e -> e.getName().equals(method)).findFirst()
                    .orElseThrow(() -> new IllegalArgumentException("指定包内未找到方法[" + methodName + "(" + method + ")]"));

            Object targetObject = targetClass.newInstance();
            Parameter[] parameters = targetMethod.getParameters();
            if (ObjectUtil.isEmpty(parameters)) {
                return targetMethod.invoke(targetObject);
            }
            List<Object> objList = new ArrayList<>();
            for (Parameter parameter : parameters) {
                String name = parameter.getName();
                if (inputParam.containsKey(name)) {
                    Object o = inputParam.get(name);
                    //类型转换
                    objList.add(convertValueParamType(parameter, o));
                }
            }
            Object[] objects = ArrayUtil.toArray(objList, Object.class);
            return targetMethod.invoke(targetObject, objects);
        } catch (Exception ex) {
            if (ex instanceof InvocationTargetException) {
                InvocationTargetException e = (InvocationTargetException) ex;
                throw new IllegalArgumentException("业务异常：" + e.getTargetException().getMessage());
            }
            throw new IllegalArgumentException("jar包方法调用异常：" + ex.getMessage());
        }
    }

    private static Map<String, List<String>> parsePackages(JarFile jarFile) {
        Enumeration<JarEntry> entries = jarFile.entries();
        List<String> files = new ArrayList<>();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            if (!jarEntry.getName().contains("META-INF")) {

                String className = jarEntry.getName();

                if (className.endsWith(".class")) {
                    className = className.substring(0, className.length() - 6).replace("/", ".");
                    files.add(className);
                }
            }
        }
        return handleBasePackage(files);
    }

    /**
     * [com.rule.jar.TestJar, com.rule2.jar.TestJar2, com.TestJar3,cn.Test4]
     * com,cn
     *
     * @param classes 类路径列表
     * @return 路径, 类映射
     */
    private static Map<String, List<String>> handleBasePackage(List<String> classes) {
        //获取所有的class路径
        Set<String> collect = classes.stream().map(e -> e.substring(0, e.lastIndexOf(".")) + StrUtil.DOT)
                .collect(Collectors.toSet());
        Map<String, List<String>> packageMap = new HashMap<>(collect.size());

        collect.forEach(e -> packageMap.put(e, classes.stream().filter(clz -> clz.startsWith(e)).collect(Collectors.toList())));
        return packageMap;
    }

    /**
     * 从Method获取结构
     *
     * @param method 方法
     * @return 结构化对象
     */
    private static JarMethodDefinition parseDefinition(Method method) {
        JarMethodDefinition parentApiPo = new JarMethodDefinition();
        parentApiPo.setMethod(method.getName());
        if (method.isAnnotationPresent(ApiOperation.class)) {
            ApiOperation annotation = method.getAnnotation(ApiOperation.class);
            parentApiPo.setMethodName(annotation.value());
            parentApiPo.setDesc(annotation.notes());
        }
        //入参出参
        Parameter[] parameters = method.getParameters();
        //解析入参
        List<MethodParameterPo> methodParameterPos = parseParameter(parameters);
        MethodParameterPo root = JarRuleHelper.buildRoot();
        root.setChildren(methodParameterPos);
        parentApiPo.setRequest(root);
        //解析出参
        Type genericReturnType = method.getGenericReturnType();
        MethodParameterPo methodParameterPo = parseClass(genericReturnType);
        parentApiPo.setRes(genericReturnType);
        parentApiPo.setResponse(methodParameterPo);
        return parentApiPo;
    }


    private static List<MethodParameterPo> parseParameter(Parameter[] parameters) {
        List<MethodParameterPo> requestList = ListUtil.toList();
        for (Parameter parameter : parameters) {
            MethodParameterPo methodParameterPo = new MethodParameterPo();
            String name = parameter.getName();
            Class<?> type = parameter.getType();
            if (!isBasicTypeClass(type)) {
                methodParameterPo = parseClass(type);
                requestList.add(methodParameterPo);
                continue;
            }
            if (parameter.isAnnotationPresent(ApiParam.class)) {
                ApiParam annotation = parameter.getAnnotation(ApiParam.class);
                methodParameterPo.setInfo(annotation.value());
                methodParameterPo.setNecessary(annotation.required());
            }

            methodParameterPo.setTypeName(JarClassUtil.convertToDataType(parameter.getType().getSimpleName()));
            methodParameterPo.setType(parameter.getType());
            methodParameterPo.setKey(name);
            methodParameterPo.setAbsoluteKey(name);
            requestList.add(methodParameterPo);
        }
        return requestList;
    }

    public static MethodParameterPo buildRoot(){
        MethodParameterPo root = new MethodParameterPo();
        root.setKey("map");
        root.setAbsoluteKey("$");
        root.setType(LinkedHashMap.class);
        root.setInfo("root");
        root.setNecessary(true);
        return root;
    }

    private static boolean isBasicTypeClass(Class<?> clazz) {
        return ClassUtil.isBasicType(clazz) || clazz.equals(String.class);
    }

    private static MethodParameterPo parseClass(Type type) {
        Class<?> clazz = null;
        try {
            clazz = (Class<?>) type;
        } catch (ClassCastException e) {
            if (type instanceof ParameterizedTypeImpl) {
                ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) type;
                Class<?> rawType = parameterizedType.getRawType();
                Type genericType;
                if (rawType.isInterface()) {
                    genericType = rawType.getGenericInterfaces()[0];
                    ParameterizedTypeImpl genericInterface1 = (ParameterizedTypeImpl) genericType;
                    Class<?> rawType1 = genericInterface1.getRawType();
                    if (!rawType1.equals(Collection.class)) {
                        throw new RuntimeException("暂不支持body的该写法");
                    }
                    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
                    Type actualTypeArgument = actualTypeArguments[0];
                    clazz = (Class<?>) actualTypeArgument;
                    MethodParameterPo fields = JarClassUtil.getFields(clazz);
                    MethodParameterPo collection = new MethodParameterPo();
                    collection.setKey(type.getTypeName())
                            .setInfo("Collection")
                            .setAbsoluteKey("[]")
                            .setType(rawType1)
                            .setTypeName(JarClassUtil.convertToDataType(rawType1.getSimpleName()))
                            .setNecessary(true)
                            .setChildren(ListUtil.of(fields));
                    return collection;
                } else {
                    JarClassUtil.parameterizedTypeMap.put(rawType, parameterizedType);
                    return parseClass(rawType);
                }
            }
        }
        return JarClassUtil.getFields(clazz);
    }

    private static boolean isValidJarFile(String fileName) {
        return fileName.endsWith(".jar");
    }

    private static Object convertValueParamType(Parameter parameter, Object value) {
        Class<?> type = parameter.getType();
        MethodParameterPo.DataType ruleResultType = JarClassUtil.convertToDataType(type.getSimpleName());
        try {
            switch (ruleResultType) {
                case STRING:
                    return StrUtil.toString(value);
                case BOOLEAN:
                    return Boolean.parseBoolean(StrUtil.toString(value));
                case NUMBER:
                    return NumberUtil.parseNumber(StrUtil.toString(value));
                case DOUBLE:
                    return Double.parseDouble(StrUtil.toString(value));
                case INT:
                    return Integer.parseInt(StrUtil.toString(value));
                default:
                    return value;
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("参数类型转换错误");
        }
    }

}
